package cn.bitkit.component.id;

import cn.bitkit.base.util.LocalDateTimeUtil;
import cn.bitkit.base.util.StringUtil;

import java.time.LocalDateTime;
import java.util.concurrent.ThreadLocalRandom;

/**
 * ULID 简单实现方法
 * @author changbo
 */
public class ULID {

    private static final char[] BASE32_CHARS = "0123456789ABCDEFGHJKMNPQRSTVWXYZ".toCharArray();
    private static final int TIMESTAMP_LENGTH = 10;
    private static final int NANO_LENGTH = 4;
    private static final int ALL_LENGTH = 26;
    private static final int[] BASE32_INDEX = new int[128];
    static {
        // 初始化字符索引表
        for (int i = 0; i < BASE32_CHARS.length; i++) {
            BASE32_INDEX[BASE32_CHARS[i]] = i;
        }
    }
    /**
     * @return BASE32
     */
    public static String getId() {
        return getId(LocalDateTime.now());
    }

    public static String getId(LocalDateTime date) {
        long time = LocalDateTimeUtil.getMilliSeconds(date);
        // 时间戳部分
        char[] ulid = new char[ALL_LENGTH];
        for (int i = TIMESTAMP_LENGTH - 1; i >= 0; i--) {
            ulid[i] = BASE32_CHARS[(int) (time & 0x1F)];
            time >>>= 5;
        }
        //nano时间部分（为了确保同一进程在同一毫秒内获取的id递增）
        long nano = System.nanoTime();
        nano = (nano < 0) ? nano + Long.MAX_VALUE : nano;
        for (int i = TIMESTAMP_LENGTH + NANO_LENGTH; i >= TIMESTAMP_LENGTH; i--) {
            ulid[i] = BASE32_CHARS[(int) (nano & 0x1F)];
            nano >>>= 5;
        }
        // 随机部分
        for (int i = TIMESTAMP_LENGTH + NANO_LENGTH; i < ALL_LENGTH; i++) {
            ulid[i] = BASE32_CHARS[ThreadLocalRandom.current().nextInt(32)];
        }
        return new String(ulid);
    }

    public static LocalDateTime toDate(String id){
        if(StringUtil.isEmpty(id) || id.length() != ALL_LENGTH){
            return null;
        }
        char[] chars = id.substring(0, TIMESTAMP_LENGTH).toCharArray();
        long time = 0;
        for(int i=0; i<TIMESTAMP_LENGTH; i++){
            char c = chars[i];
            time = (time << 5) | BASE32_INDEX[c];
        }
        return LocalDateTimeUtil.toLocalDateTime(time);
    }

}
